package sitar;
import flash.filesystem.File;
import flash.filesystem.FileStream;
import flash.Lib;
import haxe.xml.Fast;
import oboe.controllers.Controller;
import hxjson2.JSON;

/**
 * ...
 * @author Felipe Manga
 */

class Generator extends Controller
{
	var root:File;
	var config:Dynamic;
	var global:Dynamic;
	
	var parser:hscript.Parser;
	var interp:hscript.Interp;
	var parsedScripts:Hash < hscript.Expr >;
	
	
	public function new( project:File ) 
	{
		super();
		this.root = project;
		this.parsedScripts = new Hash< hscript.Expr >();
		this.global = { oldStream : null };
		this.config = { };
		this.parser = new hscript.Parser();
		this.interp = new hscript.Interp();
		this.interp.variables.set("ctx", null );
		this.interp.variables.set("global", this.global);
		this.interp.variables.set("ss", this.sigslot);
		this.interp.variables.set("Reflect", Reflect);
		this.interp.variables.set("Xml", Xml);
		var THIS = this;
		this.interp.variables.set("mkdir", function( e ) { 
			var dir = THIS.root.resolvePath(e);
			if ( !dir.exists )
			{
				dir.createDirectory();
			}
		} );
		this.interp.variables.set("sigParams", function( e ) { 
			if ( Reflect.hasField(THIS.global.signals, e) )
			{
				var sig:String = Reflect.field( THIS.global.signals, e );
				var call = "";
				var exp:Array<String> = sig.split(",");
				var i = 0;
				while ( i < exp.length )
				{
					if ( i > 0 ) call += ", ";
					var pair = exp[i].split(":");
					call += StringTools.trim( pair[0] );
					
					var obc = exp[i].split("<").length;
					var cbc = exp[i].split(">").length;
					while ( obc > cbc )
					{
						i++;
						obc += exp[i].split("<").length;
						cbc += exp[i].split(">").length;
					}
					
					i++;
				}
				return { decl : sig, call : call };
			}
			return { decl : e, call : "" };
		} );
		this.interp.variables.set("hxOut", function( e ) { THIS.global.stream.writeUTFBytes(e);  } );
		this.interp.variables.set("trace", function( e ) { THIS.sigslot.signalUp("log", [e] ); } );
		this.interp.variables.set("include", function( f:String ):Void { THIS.processFile( f ); } );
		
	}
	
	@slot function onAddToParent():Void
	{
		this.signalUp("log", ["Generating project \"", this.root.name, "\"..."]);

		var project = this.root.resolvePath("sitar.project");
		if ( !project.exists )
		{
			this.signalUp("error", ["\"", this.root.nativePath, "\" does not point to a valid Sitar project."]);
			this.selfDestruct();
			return;
		}
		
		var ps = new FileStream();
		ps.open( project, flash.filesystem.FileMode.READ );
		try
		{
			this.config = JSON.decode( ps.readUTFBytes( ps.bytesAvailable ) );
		}
		catch ( e:Dynamic )
		{
			this.signalUp( "error", [ "sitar.project file corrupt:", e ] );
			return;
		}
		ps.close();

		this.interp.variables.set("config", this.config);
		
		this.processFile( "main.hx" );
		this.signalUp("log", ["Complete."]);
	}
	
	private function processFile( name:String )
	{
		var data:String = "";
		var file = root.resolvePath(name);
		if ( !file.exists )
		{
			file = root.resolvePath(this.config.generators + "/" + name);
			if ( !file.exists )
			{
				this.signalUp( "log", [ "Could not open ", name ] );
				return;
			}
		}
		
		if ( file.isDirectory )
		{
			this.global.listing = file.getDirectoryListing();
			return;
		}
		else if ( !this.parsedScripts.exists( file.nativePath ) )
		{
			var stream = new FileStream();
			stream.open( file, flash.filesystem.FileMode.READ );
			data = stream.readUTFBytes( stream.bytesAvailable );
			stream.close();
		}
		
		var ext = file.extension.toLowerCase();
		if ( ext == "hxt" )
		{
			var outFile = this.root.resolvePath( this.global.output.path );
			if ( !outFile.exists || (outFile.exists && this.global.output.overwrite) )
			{
				this.sigslot.signalUp("log", ["Writing", this.global.output.path]);
				var oldStream:FileStream = this.global.stream;
				this.global.stream = new FileStream();
				
				this.global.stream.open( outFile, flash.filesystem.FileMode.WRITE );
				
				var program:hscript.Expr;
				if ( this.parsedScripts.exists( file.nativePath ) )
				{
					program = this.parsedScripts.get( file.nativePath );
				}
				else
				{
					var split = data.split("<?out");
					if ( split.length > 1 )
					{
						for ( i in 0 ... split.length )
						{
							var tmp = split[i].split("?>");
							tmp[0] = "hxOut('" + tmp[0].split("\\").join("\\\\").split("'").join("\\'") + "');";
							split[i] = tmp.join("");
						}
						
						data = split.join("");
					}
					program = this.parser.parseString( data );
					this.parsedScripts.set( file.nativePath, program );
				}
				
				
				var oldCtx = interp.variables.get("ctx");
				var ctx:Dynamic = { };
				ctx.data = data;
				ctx.fileName = file.name;
				ctx.className = outFile.name.split(".")[0];
				
				interp.variables.set("ctx", ctx);
				
				/* * /
				this.interp.execute( program );
				/*/
				try
				{
					var tinterp = new hscript.Interp();
					tinterp.variables = this.interp.variables;
					tinterp.execute( program );
					// this.interp.execute( program );
				}
				catch ( e:Dynamic )
				{
					this.sigslot.signalUp("error", ["Error running script '", file.name, "': ", e]);
				}
				/* */
				
				this.global.stream.close();
				interp.variables.set("ctx", oldCtx);
				this.global.stream = oldStream;
			}
			else
			{
				this.sigslot.signalUp("log", ["Skipped ", this.global.output.path]);
			}
		}
		else if ( ext == "json" )
		{
			this.global.json = JSON.decode( data, false );
		}
		else if ( ext == "hx" )
		{
			var program:hscript.Expr;
			if ( this.parsedScripts.exists( file.nativePath ) )
			{
				program = this.parsedScripts.get( file.nativePath );
			}
			else
			{
				program = this.parser.parseString( data );
				this.parsedScripts.set( file.nativePath, program );
			}
			
			/* * /
			this.interp.execute( program );
			/*/
			try
			{
				var tinterp = new hscript.Interp();
				tinterp.variables = this.interp.variables;
				tinterp.execute( program );
				// this.interp.execute( program );
			}
			catch ( e:Dynamic )
			{
				this.sigslot.signalUp("error", ["Error running script '", file.name, "': ", e]);
			}
			/* */
		}
		else
		{
			var oldCtx = interp.variables.get("ctx");
			var ctx:Dynamic = { };
			ctx.data = data;
			ctx.fileName = file.name;
			
			interp.variables.set("ctx", ctx);
			processFile( "parse_" + ext + ".hx" );
			interp.variables.set("ctx", oldCtx);
		}
	}	
}